/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.base.redis.aspect;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.iwindplus.base.domain.constant.CommonConstant.SymbolConstant;
import com.iwindplus.base.domain.enums.BizCodeEnum;
import com.iwindplus.base.domain.exception.BizException;
import com.iwindplus.base.redis.domain.annotation.RedisLock;
import com.iwindplus.base.redis.domain.constant.RedisConstant;
import com.iwindplus.base.redis.domain.property.RedisProperty;
import com.iwindplus.base.redis.service.RedisKeyResolver;
import com.iwindplus.base.redis.manager.RedissonManager;
import com.iwindplus.base.util.DatesUtil;
import jakarta.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.StringUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;

/**
 * 分布式锁切面.
 *
 * @author zengdegui
 * @since 2024/07/06 12:18
 */
@Slf4j
@Aspect
@Order(2)
public class RedisLockAspect {

    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private KeyGenerator keyGenerator;

    @Resource
    private CacheProperties cacheProperties;

    @Resource
    private RedissonManager redissonManager;

    @Resource
    private RedisProperty property;

    /**
     * 切点.
     */
    @Pointcut("@within(com.iwindplus.base.redis.domain.annotation.RedisLock) || @annotation(com.iwindplus.base.redis.domain.annotation.RedisLock)")
    public void pointCutMethod() {
    }

    /**
     * 环绕通知.
     *
     * @param joinPoint 切点
     * @return Object
     * @throws Throwable 异常
     */
    @Around("pointCutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RedisLock annotation = method.getAnnotation(RedisLock.class);
        if (Boolean.FALSE.equals(this.property.getLock().getEnabled()) || Boolean.FALSE.equals(annotation.enabled())) {
            return joinPoint.proceed();
        }

        long beginMillis = System.currentTimeMillis();
        log.info("Redis分布式锁获取，开始时间={}", DatesUtil.parseDate(beginMillis, DatePattern.NORM_DATETIME_MS_PATTERN));

        final String[] names = annotation.names();
        final String[] keys = annotation.keys();
        final Class<? extends RedisKeyResolver> resolver = annotation.keyResolver();
        final RedisKeyResolver keyResolver = this.applicationContext.getBean(resolver);

        String keyPrefix = Optional.ofNullable(this.cacheProperties.getRedis()).map(m -> m.getKeyPrefix()).orElse(null);

        String lockKey = RedisLockAspect.getRedisKey(joinPoint, this.keyGenerator,
            keyPrefix, RedisConstant.LOCK_KEY_PREFIX, names, keys, keyResolver);

        log.info("Redis分布式锁获取，lockKey={}，开始时间={}", lockKey, DatesUtil.parseDate(beginMillis, DatePattern.NORM_DATETIME_MS_PATTERN));
        return this.getResult(joinPoint, annotation, beginMillis, lockKey);
    }

    private Object getResult(ProceedingJoinPoint joinPoint, RedisLock redisLock, long beginMillis, String lockKey) throws Throwable {
        RLock lock = redissonManager.getLock(redisLock.lockType(), lockKey);
        try {
            boolean status = redissonManager.tryLock(lock, redisLock.waitTime(), redisLock.leaseTime(), redisLock.timeUnit());
            if (!status) {
                log.warn("Redis分布式锁获取失败，lockKey={}, cost={}ms", lockKey, System.currentTimeMillis() - beginMillis);
                throw new BizException(BizCodeEnum.GET_LOCK_ERROR, new Object[]{lockKey});
            }

            // 执行上锁方法
            return joinPoint.proceed();
        } finally {
            redissonManager.unlock(lock);

            final long endMillis = System.currentTimeMillis();
            log.info("Redis分布式锁释放完成，lockKey={}，结束时间={}，总执行毫秒数={}", lockKey,
                DatesUtil.parseDate(endMillis, DatePattern.NORM_DATETIME_MS_PATTERN), endMillis - beginMillis);
        }
    }

    static String getRedisKey(JoinPoint joinPoint, KeyGenerator keyGenerator,
        String keyPrefix, String bizKey, String[] names, String[] keys, RedisKeyResolver keyResolver) {
        StringBuilder sb = new StringBuilder();
        if (ObjectUtil.isNotEmpty(keyPrefix)) {
            sb.append(keyPrefix);
        }
        sb.append(bizKey);
        if (ArrayUtil.isNotEmpty(names)) {
            String name = Arrays.asList(names).stream().collect(Collectors.joining(SymbolConstant.UNDERLINE));
            sb.append(name).append(SymbolConstant.COLON);
        }
        String resolver = keyResolver.resolver(joinPoint, keyGenerator, keys);
        if (CharSequenceUtil.isBlank(resolver)) {
            resolver = StringUtil.getUUID();
        }
        sb.append(resolver);
        return sb.toString();
    }

}
